import MIDI from './MIDI'
import Base64Binary from '../../inc/shim/Base64binary'

window.AudioContext && (function() {
	var audioContext = null; // new AudioContext();
	var useStreamingBuffer = false; // !!audioContext.createMediaElementSource;
	var midi = MIDI.WebAudio = {api: 'webaudio'};
	var ctx; // audio context
	var sources = {};
	var effects = {};
	var masterVolume = 127;
	var audioBuffers = {};
	///
	midi.audioBuffers = audioBuffers;
	midi.send = function(data, delay) { };
	midi.setController = function(channelId, type, value, delay) { };

	midi.setVolume = function(channelId, volume, delay) {
		if (delay) {
			setTimeout(function() {
				masterVolume = volume;
			}, delay * 1000);
		} else {
			masterVolume = volume;
		}
	};

	midi.programChange = function(channelId, program, delay) {
// 			if (delay) {
// 				return setTimeout(function() {
// 					var channel = MIDI.channels[channelId];
// 					channel.instrument = program;
// 				}, delay);
// 			} else {
			var channel = MIDI.channels[channelId];
			channel.instrument = program;
// 			}
	};

	midi.pitchBend = function(channelId, program, delay) {
// 			if (delay) {
// 				setTimeout(function() {
// 					var channel = MIDI.channels[channelId];
// 					channel.pitchBend = program;
// 				}, delay);
// 			} else {
			var channel = MIDI.channels[channelId];
			channel.pitchBend = program;
// 			}
	};

	midi.noteOn = function(channelId, noteId, velocity, delay) {
		delay = delay || 0;

		/// check whether the note exists
		var channel = MIDI.channels[channelId];
		var instrument = channel.instrument;
		var bufferId = instrument + '' + noteId;
		var buffer = audioBuffers[bufferId];
		if (!buffer) {
// 				console.log(MIDI.GM.byId[instrument].id, instrument, channelId);
			return;
		}

		/// convert relative delay to absolute delay
		if (delay < ctx.currentTime) {
			delay += ctx.currentTime;
		}

		/// create audio buffer
		if (useStreamingBuffer) {
			var source = ctx.createMediaElementSource(buffer);
		} else { // XMLHTTP buffer
			var source = ctx.createBufferSource();
			source.buffer = buffer;
		}

		/// add effects to buffer
		if (effects) {
			var chain = source;
			for (var key in effects) {
				chain.connect(effects[key].input);
				chain = effects[key];
			}
		}

		/// add gain + pitchShift
		var gain = (velocity / 127) * (masterVolume / 127) * 2 - 1;
		source.connect(ctx.destination);
		source.playbackRate.value = 1; // pitch shift
		source.gainNode = ctx.createGain(); // gain
		source.gainNode.connect(ctx.destination);
		source.gainNode.gain.value = Math.min(1.0, Math.max(-1.0, gain));
		source.connect(source.gainNode);
		///
		if (useStreamingBuffer) {
			if (delay) {
				return setTimeout(function() {
					buffer.currentTime = 0;
					buffer.play()
				}, delay * 1000);
			} else {
				buffer.currentTime = 0;
				buffer.play()
			}
		} else {
			source.start(delay || 0);
		}
		///
		sources[channelId + '' + noteId] = source;
		///
		return source;
	};

	midi.noteOff = function(channelId, noteId, delay) {
		delay = delay || 0;

		/// check whether the note exists
		var channel = MIDI.channels[channelId];
		var instrument = channel.instrument;
		var bufferId = instrument + '' + noteId;
		var buffer = audioBuffers[bufferId];
		if (buffer) {
			if (delay < ctx.currentTime) {
				delay += ctx.currentTime;
			}
			///
			var source = sources[channelId + '' + noteId];
			if (source) {
				if (source.gainNode) {
					// @Miranet: 'the values of 0.2 and 0.3 could of course be used as
					// a 'release' parameter for ADSR like time settings.'
					// add { 'metadata': { release: 0.3 } } to soundfont files
					var gain = source.gainNode.gain;
					gain.linearRampToValueAtTime(gain.value, delay);
					gain.linearRampToValueAtTime(-1.0, delay + 0.3);
				}
				///
				if (useStreamingBuffer) {
					if (delay) {
						setTimeout(function() {
							buffer.pause();
						}, delay * 1000);
					} else {
						buffer.pause();
					}
				} else {
					if (source.noteOff) {
						source.noteOff(delay + 0.5);
					} else {
						source.stop(delay + 0.5);
					}
				}
				///
				delete sources[channelId + '' + noteId];
				///
				return source;
			}
		}
	};

	midi.chordOn = function(channel, chord, velocity, delay) {
		var res = {};
		for (var n = 0, note, len = chord.length; n < len; n++) {
			res[note = chord[n]] = midi.noteOn(channel, note, velocity, delay);
		}
		return res;
	};

	midi.chordOff = function(channel, chord, delay) {
		var res = {};
		for (var n = 0, note, len = chord.length; n < len; n++) {
			res[note = chord[n]] = midi.noteOff(channel, note, delay);
		}
		return res;
	};

	midi.stopAllNotes = function() {
		for (var sid in sources) {
			var delay = 0;
			if (delay < ctx.currentTime) {
				delay += ctx.currentTime;
			}
			var source = sources[sid];
			source.gain.linearRampToValueAtTime(1, delay);
			source.gain.linearRampToValueAtTime(0, delay + 0.3);
			if (source.noteOff) { // old api
				source.noteOff(delay + 0.3);
			} else { // new api
				source.stop(delay + 0.3);
			}
			delete sources[sid];
		}
	};

	midi.setEffects = function(list) {
		if (ctx.tunajs) {
			for (var n = 0; n < list.length; n ++) {
				var data = list[n];
				var effect = new ctx.tunajs[data.type](data);
				effect.connect(ctx.destination);
				effects[data.type] = effect;
			}
		} else {
			return console.log('Effects module not installed.');
		}
	};

	midi.connect = function(opts) {
		MIDI.setDefaultPlugin(midi);
		midi.setContext(ctx || createAudioContext(), opts.onsuccess);
	};

	midi.getContext = function() {
		return ctx;
	};

	midi.setContext = function(newCtx, onload, onprogress, onerror) {
		ctx = newCtx;

		/// tuna.js effects module - https://github.com/Dinahmoe/tuna
		if (typeof Tuna !== 'undefined' && !ctx.tunajs) {
			ctx.tunajs = new Tuna(ctx);
		}

		/// loading audio files
		var urls = [];
		var notes = MIDI.keyToNote;
		for (var key in notes) urls.push(key);
		///
		var waitForEnd = function(instrument) {
			for (var key in bufferPending) { // has pending items
				if (bufferPending[key]) return;
			}
			///
			if (onload) { // run onload once
				onload();
				onload = null;
			}
		};
		///
		var requestAudio = function(soundfont, instrumentId, index, key) {
			var url = soundfont[key];
			if (url) {
				bufferPending[instrumentId] ++;
				loadAudio(url, function(buffer) {
					buffer.id = key;
					var noteId = MIDI.keyToNote[key];
					audioBuffers[instrumentId + '' + noteId] = buffer;
					///
					if (-- bufferPending[instrumentId] === 0) {
						var percent = index / 87;
// 							console.log(MIDI.GM.byId[instrumentId], 'processing: ', percent);
						soundfont.isLoaded = true;
						waitForEnd(instrument);
					}
				}, function(err) {
	// 				console.log(err);
				});
			}
		};
		///
		var bufferPending = {};
		for (var instrument in MIDI.Soundfont) {
			var soundfont = MIDI.Soundfont[instrument];
			if (soundfont.isLoaded) {
				continue;
			}
			///
			var synth = MIDI.GM.byName[instrument];
			var instrumentId = synth.number;
			///
			bufferPending[instrumentId] = 0;
			///
			for (var index = 0; index < urls.length; index++) {
				var key = urls[index];
				requestAudio(soundfont, instrumentId, index, key);
			}
		}
		///
		setTimeout(waitForEnd, 1);
	};

	/* Load audio file: streaming | base64 | arraybuffer
	---------------------------------------------------------------------- */
	function loadAudio(url, onload, onerror) {
		if (useStreamingBuffer) {
			var audio = new Audio();
			audio.src = url;
			audio.controls = false;
			audio.autoplay = false;
			audio.preload = false;
			audio.addEventListener('canplay', function() {
				onload && onload(audio);
			});
			audio.addEventListener('error', function(err) {
				onerror && onerror(err);
			});
			document.body.appendChild(audio);
		} else if (url.indexOf('data:audio') === 0) { // Base64 string
			var base64 = url.split(',')[1];
			var buffer = Base64Binary.decodeArrayBuffer(base64);
			ctx.decodeAudioData(buffer, onload, onerror);
		} else { // XMLHTTP buffer
			var request = new XMLHttpRequest();
			request.open('GET', url, true);
			request.responseType = 'arraybuffer';
			request.onload = function() {
				ctx.decodeAudioData(request.response, onload, onerror);
			};
			request.send();
		}
	};

	function createAudioContext() {
		return new (window.AudioContext || window.webkitAudioContext)();
	};
})();
